#include <stdlib.h>
#include <string.h>

#include <windows.h>

#include "lib.h"
#include "stream.h"

pCStream
newCStream(const String & Filename, uint32 Mode)
{
	pCStream stream = (pCStream)malloc(sizeof(CStream));

	if (stream)
	{
		if (CStreamConstruct(stream, Filename, Mode) == false)
		{
			deleteCStream(stream);
			stream = (pCStream)0;
		}
	}

	return stream;
}


void
deleteCStream(pCStream This)
{
	if (This)
	{
		CStreamDestruct(This);
		free(This);
	}
}


bool8
CStreamConstruct(pCStream This, const String & Filename, uint32 Mode)
{
	DWORD accessMode = 0;
	DWORD shareMode = 0;
	DWORD createMode = 0;

	if (Mode & streamO_APPEND)
	{
		accessMode = GENERIC_WRITE;
		createMode = OPEN_ALWAYS;
	}
	else if (Mode & streamO_WRONLY)
	{
		accessMode = GENERIC_WRITE;
		createMode = CREATE_ALWAYS;
	}
	else if (Mode & streamO_RDONLY)
	{
		accessMode = GENERIC_READ;
		shareMode = FILE_SHARE_READ;
		createMode = OPEN_EXISTING;
	}
	else if (Mode & streamO_UPDATE)
	{
		accessMode = GENERIC_READ | GENERIC_WRITE;
		createMode = OPEN_EXISTING;
	}
	else
	{
		accessMode = GENERIC_READ | GENERIC_WRITE;
		createMode = CREATE_ALWAYS;
	}

	This->cFile = CreateFile((const uint16*)Filename, accessMode, shareMode,
		(LPSECURITY_ATTRIBUTES)0, createMode, FILE_ATTRIBUTE_NORMAL, (HANDLE)0);

	if (This->cFile == INVALID_HANDLE_VALUE)
		return false;

	This->cMode = Mode;

#ifdef DEBUG_CSTREAM
	This->cFilename = strdup(Filename);
#endif

	return true;
}


void
CStreamDestruct(pCStream This)
{
	if (This->cFile != INVALID_HANDLE_VALUE)
	{
		CloseHandle(This->cFile);
		This->cFile = INVALID_HANDLE_VALUE;
	}

#ifdef DEBUG_CSTREAM
	if (This->cFilename)
		free(This->cFilename);
#endif
}


//
// BINARY routines.
//
int32
CStreamRead(pCStream This, void *Buffer, uint32 Length)
{
	DWORD numRead;

	if (ReadFile(This->cFile, Buffer, Length, &numRead, (LPOVERLAPPED)0))
	{
		if (numRead == 0)
			return streamEOF;

		return (int32)numRead;
	}

	return 0;
}


int32
CStreamWrite(pCStream This, void *Buffer, uint32 Length)
{
	DWORD numWritten;

	if (This->cMode & streamO_APPEND)
		SetFilePointer(This->cFile, 0, (LPLONG)0, FILE_END);

	if (WriteFile(This->cFile, Buffer, (DWORD)Length, &numWritten,
		(LPOVERLAPPED)0))
		return (int32)numWritten;
	else
		return 0;
}


int32
CStreamSeek(pCStream This, int32 Offset, uint32 Where)
{
	DWORD	result;

	switch (Where)
	{
		case streamSEEK_SET:
			result = SetFilePointer(This->cFile, (LONG)Offset,
				(PLONG)0, FILE_BEGIN);
			break;
		case streamSEEK_CUR:
			result = SetFilePointer(This->cFile, (LONG)Offset,
				(PLONG)0, FILE_CURRENT);
			break;
		case streamSEEK_END:
			result = SetFilePointer(This->cFile, (LONG)Offset,
				(PLONG)0, FILE_END);
			break;
		default:
			result = 0xFFFFFFFFL;
			break;
	}

	if (result == 0xFFFFFFFFL)
		return streamEOF;

	return (int32)result;
}


int32
CStreamTell(pCStream This)
{
	DWORD result = SetFilePointer(This->cFile, 0, (LPLONG)0, FILE_CURRENT);

	if (result == 0xFFFFFFFFL)
		return streamEOF;

	return (int32)result;
}


//
// MISC routines.
//
int32
CStreamAccess(const String & Filename, uint32 Mode)
{
	DWORD attr = GetFileAttributes(Filename);

	if (attr == 0xFFFFFFFFL)		// handles streamF_OK
		return streamEOF;

	if (Mode & streamW_OK && ((attr & FILE_ATTRIBUTE_READONLY) != 0))
		return streamEOF;
	
	// streamR_OK & streamX_OK always true

	return 0;
}

bool8
CStreamCreateDir(const String & Path)
{
	return (bool8)(CreateDirectory(Path, (LPSECURITY_ATTRIBUTES)0) == TRUE);
}

bool8
CStreamDelFile(const String & Filename)
{
	return (bool8)(DeleteFile(Filename) == TRUE);
}

bool8	
CStreamTruncate(pCStream This, uint32 newSize)
{
	return (bool8)
		(0xFFFFFFFFL !=
			SetFilePointer(This->cFile, (LONG)newSize, 0, FILE_BEGIN) &&
		0 != SetEndOfFile(This->cFile));
}

void	
CStreamGetDosDateTime(pCStream This, uint16* lastModDate, uint16* lastModTime)
{
	FILETIME ft, lft;

	if (!GetFileTime(This->cFile, NULL, NULL, &ft) ||
		!FileTimeToLocalFileTime(&ft, &lft) ||
		!FileTimeToDosDateTime(&lft, lastModDate, lastModTime))
		*lastModTime = *lastModDate = 0;
}

int32
CStreamGetMaxFilePath()
{
	return MAX_PATH;
}
